package com.wirelesspienetwork.overview.views;

import com.wirelesspienetwork.overview.misc.OverviewConfiguration;
import ohos.agp.components.Component;
import ohos.agp.components.VelocityDetector;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

public class OverviewStackViewTouchHandler implements SwipeHelper.Callback {

    static int INACTIVE_POINTER_ID = -1;
    static final int TaskStackOverscrollRange = 150;

    OverviewConfiguration mConfig;
    OverviewStackView mSv;
    OverviewStackViewScroller mScroller;
    VelocityDetector mVelocityTracker;

    boolean mIsScrolling;

    float mInitialP;
    float mLastP;
    float mTotalPMotion;
    int mInitialMotionX, mInitialMotionY;
    int mLastMotionX, mLastMotionY;
    int mActivePointerId = INACTIVE_POINTER_ID;
    OverviewCard mActiveTaskView = null;

    int mMinimumVelocity;
    int mMaximumVelocity;

    // The scroll touch slop is used to calculate when we start scrolling
    int mScrollTouchSlop;

    // The page touch slop is used to calculate when we start swiping
    float mPagingTouchSlop;
    SwipeHelper mSwipeHelper;
    boolean mInterceptedBySwipeHelper;

    private final int VIEW_CONFIGURATION_TOUCH_SLOP = 24;
    private final int VIEW_CONFIGURATION_MINIMUMVELOCITY = 150;
    private final int VIEW_CONFIGURATION_MAXIMUMVELOCITY = 18000;

    int mInitialTouchPos;
    private Component mCurrView;

    public OverviewStackViewTouchHandler(Context context, OverviewStackView sv,
                                         OverviewConfiguration config, OverviewStackViewScroller scroller) {
        mMinimumVelocity = VIEW_CONFIGURATION_MINIMUMVELOCITY;
        mMaximumVelocity = VIEW_CONFIGURATION_MAXIMUMVELOCITY;
        mScrollTouchSlop = VIEW_CONFIGURATION_TOUCH_SLOP;
        mPagingTouchSlop = 48.0f;
        mSv = sv;
        mScroller = scroller;
        mConfig = config;

        float densityScale = 3.0f;
        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop, mConfig);
    }

    /** Velocity tracker helpers */
    void initOrResetVelocityTracker() {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityDetector.obtainInstance();
        } else {
            mVelocityTracker.clear();
        }
    }

    void initVelocityTrackerIfNotExists() {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityDetector.obtainInstance();
        }
    }

    void recycleVelocityTracker() {
        if (mVelocityTracker != null) {
            mVelocityTracker.clear();
            mVelocityTracker = null;
        }
    }

    /** Returns the view at the specified coordinates
     * @param x
     * @param y
     * @return OverviewCard */
    OverviewCard findViewAtPoint(int x, int y) {
        int childCount = mSv.getChildCount();
        for (int i = childCount - 1; i >= 0; i--) {
            OverviewCard tv = (OverviewCard) mSv.getComponentAt(i);
            if (tv.getVisibility() == Component.VISIBLE) {
                if (mSv.isTransformedTouchPointInView(x, y, tv)) {
                    return tv;
                }
            }
        }
        return null;
    }

    /** Constructs a simulated motion event for the current stack scroll.
     * @param ev
     * @return TouchEvent
     * */
    TouchEvent createMotionEventForStackScroll(TouchEvent ev) {
        TouchEvent pev = (ev);
        pev.setScreenOffset(0, mScroller.progressToScrollRange(mScroller.getStackScroll()));
        return pev;
    }

    /** Touch preprocessing for handling below
     * @param ev
     * @return boolean */
    public boolean onInterceptTouchEvent(TouchEvent ev) {
        // Return early if we have no children
        boolean hasChildren = (mSv.getChildCount() > 0);
        if (!hasChildren) {
            return false;
        }

        // Pass through to swipe helper if we are swiping
        mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
        if (mInterceptedBySwipeHelper) {
            return true;
        }

        boolean wasScrolling = mScroller.isScrolling() ||
                (mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning());
        int action = ev.getAction();
        switch (action /*& TouchEvent..ACTION_MASK*/) {
            case TouchEvent.PRIMARY_POINT_DOWN: {
                // Save the touch down info
                mInitialMotionX = mLastMotionX = (int) ev.getPointerPosition(0).getX();
                mInitialMotionY = mLastMotionY = (int) ev.getPointerPosition(0).getY();
                mInitialP = mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
                mActivePointerId = ev.getPointerId(0);
                mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
                // Stop the current scroll if it is still flinging
                mScroller.stopScroller();
                mScroller.stopBoundScrollAnimation();
                // Initialize the velocity tracker
                initOrResetVelocityTracker();
                mVelocityTracker.addEvent(createMotionEventForStackScroll(ev));
                // Check if the scroller is finished yet
                mIsScrolling = mScroller.isScrolling();
                break;
            }
            case TouchEvent.POINT_MOVE: {
                if (mActivePointerId == INACTIVE_POINTER_ID) break;

                int activePointerIndex = ev.getPointerId(mActivePointerId);
                int y = (int) ev.getPointerPosition(activePointerIndex).getY();
                int x = (int) ev.getPointerPosition(activePointerIndex).getX();
                if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
                    // Save the touch move info
                    mIsScrolling = true;
                    // Initialize the velocity tracker if necessary
                    initVelocityTrackerIfNotExists();
                    mVelocityTracker.addEvent(createMotionEventForStackScroll(ev));
                }

                mLastMotionX = x;
                mLastMotionY = y;
                mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
                break;
            }
            case TouchEvent.CANCEL:
            case TouchEvent.PRIMARY_POINT_UP: {
                // Animate the scroll back if we've cancelled
                mScroller.animateBoundScroll();
                // Reset the drag state and the velocity tracker
                mIsScrolling = false;
                mActivePointerId = INACTIVE_POINTER_ID;
                mActiveTaskView = null;
                mTotalPMotion = 0;
                recycleVelocityTracker();
                break;
            }
        }
        return wasScrolling || mIsScrolling;
    }

    public boolean onTouchEvent(TouchEvent ev) {
        boolean hasChildren = (mSv.getChildCount() > 0);

        if (!hasChildren) {
            return false;
        }

        initVelocityTrackerIfNotExists();
        int action = ev.getAction();
        switch (action) {
            case TouchEvent.PRIMARY_POINT_DOWN:{
                // Save the touch down info
                mInitialMotionX = mLastMotionX = (int) ev.getPointerPosition(0).getX();
                mInitialMotionY = mLastMotionY = (int) ev.getPointerPosition(0).getY();
                mInitialP = mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
                mActivePointerId = ev.getPointerId(0);
                mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
                // Stop the current scroll if it is still flinging
                mScroller.stopScroller();
                mScroller.stopBoundScrollAnimation();
                // Initialize the velocity tracker
                initOrResetVelocityTracker();
                mVelocityTracker.addEvent(createMotionEventForStackScroll(ev));
                mInitialTouchPos = (int) mSwipeHelper.getPos(ev);
                break;
            }

            case TouchEvent.OTHER_POINT_DOWN: {
                final int index = ev.getIndex();
                mActivePointerId = ev.getPointerId(index);
                mLastMotionX = (int) ev.getPointerPosition(index).getX();
                mLastMotionY = (int) ev.getPointerPosition(index).getY();
                mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
                break;
            }

            case TouchEvent.POINT_MOVE:{
                if (mActivePointerId == INACTIVE_POINTER_ID) break;
                int activePointerIndex = ev.getPointerId(mActivePointerId);
                int x = (int) ev.getPointerPosition(activePointerIndex).getX();
                int y = (int) ev.getPointerPosition(activePointerIndex).getY();
                int yTotal = Math.abs(y - mInitialMotionY);
                float curP = mSv.mLayoutAlgorithm.screenYToCurveProgress(y);
                float deltaP = mLastP - curP;
                if (!mIsScrolling) {
                    if (yTotal > mScrollTouchSlop) {
                        mIsScrolling = true;
                        // Initialize the velocity tracker
                        initOrResetVelocityTracker();
                        mVelocityTracker.addEvent(createMotionEventForStackScroll(ev));
                    }
                }
                if (mIsScrolling) {
                    float curStackScroll = mScroller.getStackScroll();
                    float overScrollAmount = mScroller.getScrollAmountOutOfBounds(curStackScroll + deltaP);
                    if (Float.compare(overScrollAmount, 0f) != 0) {
                        // Bound the overscroll to a fixed amount, and inversely scale the y-movement
                        // relative to how close we are to the max overscroll
                        float maxOverScroll = mConfig.taskStackOverscrollPct;
                        deltaP *= (1f - (Math.min(maxOverScroll, overScrollAmount)
                                / maxOverScroll));
                    }
                    mScroller.setStackScroll(curStackScroll + deltaP);
                    if (mScroller.isScrollOutOfBounds()) {
                        mVelocityTracker.clear();
                    } else {
                        mVelocityTracker.addEvent(createMotionEventForStackScroll(ev));
                    }
                }
                mLastMotionX = x;
                mLastMotionY = y;
                mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
                mTotalPMotion += Math.abs(deltaP);
                break;
            }
            case TouchEvent.PRIMARY_POINT_UP: {
                Direction direction = getDirection(mInitialMotionX,mInitialMotionY,ev.getPointerPosition(0).getX(),ev.getPointerPosition(0).getY());
                if (direction == Direction.left || direction == Direction.right){
                  if ((mInitialMotionX < ev.getPointerPosition(0).getX()) || (mInitialMotionX > ev.getPointerPosition(0).getX()) )
                   {
                       if (mSwipeHelper.onTouchEvent(ev, mInitialTouchPos, getChildAtPosition(ev))) {
                           return true;
                       }
                   }
                }

                final VelocityDetector velocityTracker = mVelocityTracker;
                velocityTracker.calculateCurrentVelocity(1000,mMaximumVelocity,mMaximumVelocity);
                int velocity = (int) velocityTracker.getVerticalVelocity();
                if(mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
                    int overscrollRange = (int) (Math.min(1f,
                            Math.abs((float) velocity / mMaximumVelocity)) *
                            TaskStackOverscrollRange);
                    mScroller.mScroller.doFling(0, mScroller.progressToScrollRange(mScroller.getStackScroll()),0,velocity,0,0, mScroller.progressToScrollRange(mSv.mLayoutAlgorithm.mMinScrollP),
                            mScroller.progressToScrollRange(mSv.mLayoutAlgorithm.mMaxScrollP));
                    mSv.invalidate();
                } else if (mScroller.isScrollOutOfBounds()) {
                    // Animate the scroll back into bounds
                    mScroller.animateBoundScroll();
                }
                mActivePointerId = INACTIVE_POINTER_ID;
                mIsScrolling = false;
                mTotalPMotion = 0;
                recycleVelocityTracker();
                break;
            }

            case TouchEvent.OTHER_POINT_UP: {
                int pointerIndex = ev.getIndex();
                int pointerId = ev.getPointerId(pointerIndex);
                if (pointerId == mActivePointerId) {
                    // Select a new active pointer id and reset the motion state
                    final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
                    mActivePointerId = ev.getPointerId(newPointerIndex);
                    mLastMotionX = (int) ev.getPointerPosition(newPointerIndex).getX();
                    mLastMotionY = (int) ev.getPointerPosition(newPointerIndex).getY();
                    mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY);
                    mVelocityTracker.clear();
                }
                break;
            }

            case TouchEvent.CANCEL: {
                if (mScroller.isScrollOutOfBounds()) {
                    // Animate the scroll back into bounds
                    mScroller.animateBoundScroll();
                }
                mActivePointerId = INACTIVE_POINTER_ID;
                mIsScrolling = false;
                mTotalPMotion = 0;
                recycleVelocityTracker();
                break;
            }
        }
        return true;
    }

    public Direction getDirection(float x1, float y1, float x2, float y2){
        double angle = getAngle(x1, y1, x2, y2);
        return Direction.fromAngle(angle);
    }

    /**
     *
     * Finds the angle between two points in the plane (x1,y1) and (x2, y2)
     * The angle is measured with 0/360 being the X-axis to the right, angles
     * increase counter clockwise.
     *
     * @param x1 the x position of the first point
     * @param y1 the y position of the first point
     * @param x2 the x position of the second point
     * @param y2 the y position of the second point
     * @return the angle between two points
     */
    public double getAngle(float x1, float y1, float x2, float y2) {

        double rad = Math.atan2(y1-y2,x2-x1) + Math.PI;
        return (rad*180/Math.PI + 180)%360;
    }


    public enum Direction{
        up,
        down,
        left,
        right;

        /**
         * Returns a direction given an angle.
         * Directions are defined as follows:
         *
         * Up: [45, 135]
         * Right: [0,45] and [315, 360]
         * Down: [225, 315]
         * Left: [135, 225]
         *
         * @param angle an angle from 0 to 360 - e
         * @return the direction of an angle
         */
        public static Direction fromAngle(double angle){
            if(inRange(angle, 45, 135)){
                return Direction.up;
            }
            else if(inRange(angle, 0,45) || inRange(angle, 315, 360)){
                return Direction.right;
            }
            else if(inRange(angle, 225, 315)){
                return Direction.down;
            }
            else{
                return Direction.left;
            }

        }

        /** count range
         * @param angle
         * @param init
         * @param end
         * @return boolean
         */
        private static boolean inRange(double angle, float init, float end){
            return (angle >= init) && (angle < end);
        }
    }

    /**** SwipeHelper Implementation
     * @param ev
     * @return Component ****/

    @Override
    public Component getChildAtPosition(TouchEvent ev) {
        return findViewAtPoint((int) ev.getPointerPosition(0).getX(), (int) ev.getPointerPosition(0).getY());
    }

    @Override
    public boolean canChildBeDismissed(Component v) {
        return true;
    }

    @Override
    public void onBeginDrag(Component v) {
        OverviewCard tv = (OverviewCard) v;
        // Disallow touch events from this task view
        tv.setTouchEnabled(false);
    }

    @Override
    public void onSwipeChanged(Component v, float delta) {
        // Do nothing
    }

    @Override
    public void onChildDismissed(Component v) {
        if (v == null) {
            return;
        }
        OverviewCard tv = (OverviewCard) v;
        // Re-enable touch events from this task view
        tv.setTouchEnabled(true);
        // Remove the task view from the stack
        mSv.onCardDismissed(tv);
    }

    @Override
    public void onSnapBackCompleted(Component v) {
        OverviewCard tv = (OverviewCard) v;
        // Re-enable touch events from this task view
        tv.setTouchEnabled(true);
    }

    @Override
    public void onDragCancelled(Component v) {
        // Do nothing
    }
}
